#include "Inf_ES8311.h"
#include "Inf_PCA9557.h"
#include "driver/i2s_std.h"
#include "es8311.h"
#include "esp_check.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include <stdio.h>
#include <string.h>

static const char *TAG = "i2s_es8311";
static const char err_reason[][30] = {"input param is invalid", "operation timeout"};
static i2s_chan_handle_t tx_handle = NULL;

// 播放音乐
extern const uint8_t music_pcm_start[] asm("_binary_canon_pcm_start");
extern const uint8_t music_pcm_end[] asm("_binary_canon_pcm_end");

/**
 * @brief 初始化 ES8311 音频编解码器
 *
 * 该函数用于初始化 I2C 总线，并对 ES8311 音频编解码器进行一系列配置，
 * 包括创建编解码器句柄、配置时钟、设置采样频率、音量、麦克风等参数。
 *
 * @return esp_err_t 操作结果，ESP_OK 表示成功，其他值表示错误
 */
static esp_err_t es8311_codec_init(void) {

  // 初始化I2c,但是I2c我们以前使用过
  ESP_ERROR_CHECK(bsp_i2c_init());

  /* 初始化 ES8311 编解码器 */
  es8311_handle_t es_handle = es8311_create(I2C_NUM_0, ES8311_ADDRRES_0);
  // 检查设备句柄是否创建成功，若失败则返回错误
  ESP_RETURN_ON_FALSE(es_handle, ESP_FAIL, TAG, "es8311 create failed");
  // 配置 ES8311 的时钟参数
  const es8311_clock_config_t es_clk = {.mclk_inverted = false,
                                        .sclk_inverted = false,
                                        .mclk_from_mclk_pin = true,
                                        .mclk_frequency = EXAMPLE_MCLK_FREQ_HZ,
                                        .sample_frequency = EXAMPLE_SAMPLE_RATE};

  ESP_ERROR_CHECK(es8311_init(es_handle, &es_clk, ES8311_RESOLUTION_16, ES8311_RESOLUTION_16));
  ESP_RETURN_ON_ERROR(es8311_sample_frequency_config(es_handle, EXAMPLE_SAMPLE_RATE * EXAMPLE_MCLK_MULTIPLE, EXAMPLE_SAMPLE_RATE), TAG,
                      "set es8311 sample frequency failed");
  ESP_RETURN_ON_ERROR(es8311_voice_volume_set(es_handle, EXAMPLE_VOICE_VOLUME, NULL), TAG, "set es8311 volume failed");
  ESP_RETURN_ON_ERROR(es8311_microphone_config(es_handle, false), TAG, "set es8311 microphone failed");
  return ESP_OK;
}

static esp_err_t i2s_driver_init(void) {
  i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM, I2S_ROLE_MASTER);
  chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer
  ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL));
  i2s_std_config_t std_cfg = {
      .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(EXAMPLE_SAMPLE_RATE),
      .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
      .gpio_cfg =
          {
              .mclk = I2S_MCK_IO,
              .bclk = I2S_BCK_IO,
              .ws = I2S_WS_IO,
              .dout = I2S_DO_IO,
              .din = I2S_DI_IO,
              .invert_flags =
                  {
                      .mclk_inv = false,
                      .bclk_inv = false,
                      .ws_inv = false,
                  },
          },
  };
  std_cfg.clk_cfg.mclk_multiple = EXAMPLE_MCLK_MULTIPLE;

  // 以标准模式初始化 I2S 发送通道，使用之前配置好的标准配置结构体 std_cfg
  ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg));
  // 启用 I2S 发送通道，使其可以开始发送数据
  ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));

  return ESP_OK;
}

/**
 * @brief 播放 PCM 音乐数据的任务函数
 *
 * 该任务函数负责将 PCM 音乐数据通过 I2S 发送通道发送到耳机播放。
 * 它会先禁用 I2S 发送通道并预加载数据，然后启用通道开始持续播放音乐。
 * 如果发送过程中出现错误，任务会输出错误信息并终止。
 *
 * @param args 任务参数，本函数未使用该参数
 */
static void i2s_music(void *args) {

  MY_LOGE("播放音乐");
  esp_err_t ret = ESP_OK;
  size_t bytes_write = 0;
  uint8_t *data_ptr = (uint8_t *)music_pcm_start;

  /* (Optional) Disable TX channel and preload the data before enabling the TX channel,
   * so that the valid data can be transmitted immediately */
  ESP_ERROR_CHECK(i2s_channel_disable(tx_handle));
  ESP_ERROR_CHECK(i2s_channel_preload_data(tx_handle, data_ptr, music_pcm_end - data_ptr, &bytes_write));
  data_ptr += bytes_write; // Move forward the data pointer

  /* Enable the TX channel */
  ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));
  while (1) {
    /* Write music to earphone */
    ret = i2s_channel_write(tx_handle, data_ptr, music_pcm_end - data_ptr, &bytes_write, portMAX_DELAY);
    if (ret != ESP_OK) {
      /* Since we set timeout to 'portMAX_DELAY' in 'i2s_channel_write'
         so you won't reach here unless you set other timeout value,
         if timeout detected, it means write operation failed. */
      ESP_LOGE(TAG, "[music] i2s write failed, %s", err_reason[ret == ESP_ERR_TIMEOUT]);
      abort();
    }
    if (bytes_write > 0) {
      ESP_LOGI(TAG, "[music] i2s music played, %d bytes are written.", bytes_write);
    } else {
      ESP_LOGE(TAG, "[music] i2s music play failed.");
      abort();
    }
    data_ptr = (uint8_t *)music_pcm_start;
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
  vTaskDelete(NULL);
}

// 初始化
void Inf_ES8311_Init(void) {
  printf("i2s es8311 codec example start\n-----------------------------\n");
  /* Initialize i2s peripheral */
  if (i2s_driver_init() != ESP_OK) {
    ESP_LOGE(TAG, "i2s driver init failed");
    abort();
  } else {
    ESP_LOGI(TAG, "i2s driver init success");
  }
  /* Initialize i2c peripheral and config es8311 codec by i2c */
  if (es8311_codec_init() != ESP_OK) {
    ESP_LOGE(TAG, "es8311 codec init failed");
    abort();
  } else {
    ESP_LOGI(TAG, "es8311 codec init success");
  }

  // 配置
  Inf_PCA9557_Init();
  // 使能
  Inf_pa_en(1);

  /* Play a piece of music in music mode */
  xTaskCreate(i2s_music, "i2s_music", 4096, NULL, 5, NULL);
}